﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Diagnostics;

using Noris.Schedule.Support.Core;
using System.Data.SqlTypes;

namespace Noris.Schedule.Support
{
	#region CLASS TestCls : spouštěč testů
	public class TestCls
	{
		#region PUBLIC VSTUPNÍ BODY TESTŮ
		/// <summary>
		/// Spouštěcí metoda všech testů všech assembly
		/// </summary>
		public static void StartTestAll()
		{
			_StartTestAll(TestScopeType.All, null);
		}
		/// <summary>
		/// Spouštěcí metoda všech testů všech assembly
		/// </summary>
		/// <param name="scope">Rozsah spouštěných testů</param>
		public static void StartTestAll(TestScopeType scope)
		{
			_StartTestAll(scope, null);
		}
		/// <summary>
		/// Spouštěcí metoda všech testů všech assembly
		/// </summary>
		public static void StartTestAll(CallbackTargetDelegate callBack)
		{
			_StartTestAll(TestScopeType.All, callBack);
		}
		/// <summary>
		/// Spouštěcí metoda všech testů všech assembly
		/// </summary>
		/// <param name="scope">Rozsah spouštěných testů</param>
		/// <param name="callBack">Metoda, která přebírá a zobrazuje informace o průběhu testu. Může být null.</param>
		public static void StartTestAll(TestScopeType scope, CallbackTargetDelegate callBack)
		{
			_StartTestAll(scope, callBack);
		}
		/// <summary>
		/// Spouštěcí metoda všech testů jedné assembly
		/// </summary>
		/// <param name="assembly">Assembly, jejíž testy chceme spouštět</param>
		/// <param name="scope">Rozsah spouštěných testů</param>
		/// <param name="callBack">Metoda, která přebírá a zobrazuje informace o průběhu testu. Může být null.</param>
		public static void StartTestAss(Assembly assembly, TestScopeType scope)
		{
			_StartTestAss(assembly, scope, null);
		}
		/// <summary>
		/// Spouštěcí metoda všech testů jedné assembly
		/// </summary>
		/// <param name="assembly">Assembly, jejíž testy chceme spouštět</param>
		/// <param name="scope">Rozsah spouštěných testů</param>
		/// <param name="callBack">Metoda, která přebírá a zobrazuje informace o průběhu testu. Může být null.</param>
		public static void StartTestAss(Assembly assembly, TestScopeType scope, CallbackTargetDelegate callBack)
		{
			_StartTestAss(assembly, scope, callBack);
		}
		#endregion
		#region PRIVATE VÝKONNÉ METODY
		/// <summary>
		/// Spouštěcí metoda všech testů všech assembly
		/// </summary>
		/// <param name="scope">Rozsah spouštěných testů</param>
		/// <param name="callBack">Metoda, která přebírá a zobrazuje informace o průběhu testu. Může být null.</param>
		private static void _StartTestAll(TestScopeType scope, CallbackTargetDelegate callBack)
		{
			_CallBackMessage(callBack, CallbackState.BeginProcess, null);   // Zavolá Callback metodu, zajistí zobrazení záhlaví procesu
			// foreach loaded assembly... ?
			Assembly assembly = Assembly.GetExecutingAssembly();
			_StartTestAssExec(assembly, scope, callBack);
			
			// end foreach.
			_CallBackMessage(callBack, CallbackState.EndProcessSuccess, null);
		}
		/// <summary>
		/// Spouštěcí metoda všech testů jedné assembly.
		/// Obsahuje volání callback (BeginProcess -- EndProcessSuccess).
		/// Je určena pro test jediné assembly v rámci procesu.
		/// </summary>
		/// <param name="assembly">Assembly, jejíž testy chceme spouštět</param>
		/// <param name="scope">Rozsah spouštěných testů</param>
		/// <param name="callBack">Metoda, která přebírá a zobrazuje informace o průběhu testu. Může být null.</param>
		public static void _StartTestAss(Assembly assembly, TestScopeType scope, CallbackTargetDelegate callBack)
		{
			_CallBackMessage(callBack, CallbackState.BeginProcess, null);   // Zavolá Callback metodu, zajistí zobrazení záhlaví procesu
			_StartTestAssExec(assembly, scope, callBack);
			_CallBackMessage(callBack, CallbackState.EndProcessSuccess, null);
		}
		/// <summary>
		/// Spouštěcí metoda všech testů jedné assembly.
		/// Neobsahuje volání callback (BeginProcess -- EndProcessSuccess).
		/// Je určena pro postupné testování více assembly v rámci jednoho společného procesu.
		/// </summary>
		/// <param name="assembly">Assembly, jejíž testy chceme spouštět</param>
		/// <param name="scope">Rozsah spouštěných testů</param>
		/// <param name="callBack">Metoda, která přebírá a zobrazuje informace o průběhu testu. Může být null.</param>
		public static void _StartTestAssExec(Assembly assembly, TestScopeType scope, CallbackTargetDelegate callBack)
		{
			TestResults testResults = new TestResults();   // Soupis výsledků testů
			bool cancel = false;

			// Zahájení sekce = jedna Assembly:
			_CallBackMessage(callBack, CallbackState.BeginSection, assembly.Location);   // Zavolá Callback metodu, zajistí zobrazení záhlaví skupiny (sekce)
			bool isAnyTest = false;

			List<Type> types = new List<Type>(assembly.GetTypes());  // Soupis všech typů (třídy, struktury, interface, enumy, atd atd)
			types.Sort(_CompareTypeByHierAndFullName);     // Setřídit typy podle dědičnosti a podle FullName
			foreach (Type type in types)
			{
				if (!type.IsClass) continue;               // Pokud nalezený typ není class, jdi na další. Testujeme jen třídy.

				// Máme nalezenou třídu (class). Najdeme všechny její public static metody (jiné nemohou být vstupními metodami testů).
				List<MethodInfo> methods = new List<MethodInfo>(type.GetMethods(BindingFlags.Public | BindingFlags.Static));
				methods.Sort(_CompareMethodInfoByMethodName);
				foreach (MethodInfo mi in methods)
				{
					if (mi.GetParameters().Length == 0 && !mi.IsAbstract)
					{	// Metoda je bezparametrická, a není abstraktní, podíváme se zda má atribut "TestPoint":
						Attribute[] ats = Attribute.GetCustomAttributes(mi, typeof(TestPointAttribute));
						foreach (Attribute at in ats)
						{	// Metoda odpovídá (jde o public static metodu určité třídy v daném assembly, a má potřebný atribut).
							TestPointAttribute atpa = (TestPointAttribute)at;
							if ((atpa.TestScope & scope) == 0) continue; // Pokud oblast testu neodpovídá zadání, nebudeme jej spouštět
							if ((atpa.TestScope & TestScopeType.Disabled) == TestScopeType.Disabled) continue; // Pokud test má nahozen atribut Disabled, neprovede se.
							cancel = _StartTestOne(mi, atpa.Name, testResults, callBack);   // Spustíme metodu - což je jeden test (uvedeného jména)
							isAnyTest = true;
							break;                         // Další atribut by tam být neměl, a když už je, tak jej nebudeme číst.
						}
					}
					if (cancel) break;
				}
				if (cancel) break;
			}

			if (!isAnyTest)
			{	// Nebyly testy, vypíšeme Warning:
				_CallBackMessage(callBack, CallbackState.BeginLoop, "Nenalezen žádný odpovídající test.");
				_CallBackMessage(callBack, CallbackState.EndLoopWarning, null);
			}

			// Konec sekce:
			_CallBackMessage(callBack, CallbackState.EndSectionSuccess, assembly.Location);   // Zavolá Callback metodu, konec sekce = assembly
		}
		/// <summary>
		/// Komparátor dvou typů podle jejich dědičnosti (předek dřív, potomek potom; class dřív, subclass potom),
		/// a pro nezávislé typy jen podle jejich FullName.
		/// </summary>
		/// <param name="x"></param>
		/// <param name="y"></param>
		/// <returns>+1 = x je po y; -1 = x je před y; 0 = jsou shodné (</returns>
		private static int _CompareTypeByHierAndFullName(Type x, Type y)
		{
			// Dědičnost:
			if (_IsDerivedFrom(x, y)) // Třída x je potomkem třídy y, pak musí jít třída y (předek) napřed = vracíme +1
				return 1;
			if (_IsDerivedFrom(y, x)) // Třída y je potomkem třídy x, pak musí jít třída x (předek) napřed = vracíme -1
				return -1;

			// Subclass:
			if (x.IsSubclassOf(y))    // Třída x je subclass (vnořená třída) třídy y, zde musí jít y napřed = vracíme +1
				return 1;
			if (y.IsSubclassOf(x))    // Třída y je subclass (vnořená třída) třídy x, zde musí jít x napřed = vracíme -1
				return -1;

			// Fullname:
			return x.FullName.CompareTo(y.FullName);
		}
		/// <summary>
		/// Vrací true, pokud testovaný typ (testType) je přímým nebo vzdáleným potomkem typu (baseType)
		/// </summary>
		/// <param name="testType">Testovaný typ, potomek</param>
		/// <param name="baseType">Jiný typ, mohl by být jeho předkem?</param>
		/// <returns></returns>
		private static bool _IsDerivedFrom(Type testType, Type baseType)
		{
			if (baseType == null) return false;
			if (_IsEqualType(baseType, typeof(object))) return true;            // Každý typ je potomkem objectu
			Type tt = testType;
			while (true)
			{
				if (tt == null) return false;                                   // Nedefinovaný typ
				if (tt.BaseType == null) return false;                          // Pokud BaseType == null, pak v tt je object => vracíme false (V dědičnosti jsme došli ke kořeni typů)
				if (_IsEqualType(tt.BaseType, baseType)) return true;           // tt je potomkem baseType => vracíme true
				tt = tt.BaseType;
			}
		}
		/// <summary>
		/// Vrací true, pokud dané dva typy jsou shodné
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		private static bool _IsEqualType(Type a, Type b)
		{
			if (a == null || b == null) return false;
			return (a.GUID.CompareTo(b.GUID) == 0);
		}
		/// <summary>
		/// Komparátor dvou typů podle jejich FullName.
		/// </summary>
		/// <param name="x"></param>
		/// <param name="y"></param>
		/// <returns></returns>
		private static int _CompareTypeByFullName(Type x, Type y)
		{
			return x.FullName.CompareTo(y.FullName);
		}
		/// <summary>
		/// Komparátor dvou typů podle jejich Name.
		/// </summary>
		/// <param name="x"></param>
		/// <param name="y"></param>
		/// <returns></returns>
		private static int _CompareMethodInfoByMethodName(MethodInfo x, MethodInfo y)
		{
			return x.Name.CompareTo(y.Name);
		}
		/// <summary>
		/// Spuštění jednoho testu
		/// </summary>
		/// <param name="methodInfo">Spouštěná metoda</param>
		/// <param name="testName">Název testu</param>
		/// <param name="testResults">Soupis výsledků</param>
		/// <param name="callBack">Metoda, která přebírá a zobrazuje informace o průběhu testu. Může být null.</param>
		private static bool _StartTestOne(MethodInfo methodInfo, string testName, TestResults testResults, CallbackTargetDelegate callBack)
		{
			bool cancel = false;
			Stopwatch sw = Stopwatch.StartNew();
			try
			{
				testResults.AddTest(methodInfo, testName);
				cancel = _CallBackMessage(callBack, CallbackState.BeginLoop, testName);
				if (cancel)
				{
					throw new Exception("Uživatel zastavil proces testu.");
				}
				else
				{
					object[] parameters = new object[0];
					Assert.Section = "";
					object result = methodInfo.Invoke(null, parameters);
					sw.Stop();
					testResults.SetOK(result);
					cancel = _CallBackMessage(callBack, CallbackState.EndLoopSuccess, testResults.CurrentItem.ReturnValue);
				}
			}
			catch (Exception exc)
			{
				sw.Stop();
				testResults.SetError(exc);
				string message = testResults.CurrentItem.ErrorMessage;
				cancel = _CallBackMessage(callBack, CallbackState.EndLoopError, message);
			}
			finally
			{
				testResults.SetTime(sw);
			}
			return cancel;
		}
		/// <summary>
		/// Vyvolávač Callback metody pro zobrazení informací o průběhu.
		/// Vrací požadavek na storno procesu (true = Cancel).
		/// </summary>
		/// <param name="callBack">Delegát metody Callback</param>
		/// <param name="callbackState">Stav procesu</param>
		/// <param name="textInfo">Jméno testu</param>
		/// <returns>Požadavek Cancel (true = zastavit)</returns>
		private static bool _CallBackMessage(CallbackTargetDelegate callBack, CallbackState callbackState, string textInfo)
		{
			if (callBack == null) return false;

			CallbackArgs e;
			switch (callbackState)
			{
				case CallbackState.BeginProcess:              // Začátek celého procesu
					e = new CallbackArgs(callbackState, textInfo);
					break;

				case CallbackState.BeginSection:              // Zahájení testů jedné Assembly = zahájení jedné sekce
					// Na vstupu je v textInfo plné jméno assembly (path + name + extension).
					// Vytvoříme z toho jméno klíče a text skupiny:
					string groupKey = System.IO.Path.GetFileNameWithoutExtension(textInfo);
					string groupName = "Assembly: " + groupKey;
					e = new CallbackArgs(callbackState, groupKey, groupName);
					break;

				case CallbackState.BeginLoop:                 // Začátek jednoho testu
					e = new CallbackArgs(callbackState, textInfo);
					break;

				case CallbackState.EndLoopSuccess:
				case CallbackState.EndLoopWarning:
				case CallbackState.EndLoopError:
					e = new CallbackArgs(callbackState, textInfo);
					break;

				case CallbackState.EndProcessSuccess:
				case CallbackState.EndProcessWarning:
				case CallbackState.EndProcessError:
					e = new CallbackArgs(callbackState, textInfo);
					break;

				default:
					e = new CallbackArgs(callbackState);
					break;
			}
			callBack(null, e);
			return e.Cancel;
		}
		#endregion
	}
	#endregion
	#region CLASS Assert : kontroly hodnot v testech, vyhození výjimek při nesrovnalosti
	/// <summary>
	/// Třída, obsahující statické metody určené pro kontrolu dat v testech a následné vyhazování výjimek typu TestException
	/// </summary>
	public static class Assert
	{
		/// <summary>
		/// Aktuální sekce testu. Vypisuje se před hlášku o chybě. Při spuštění metody testu je prázdné.
		/// </summary>
		public static string Section { get { return _Section; } set { _Section = value; } }
		private static string _Section;
		/// <summary>
		/// Ověří, že hodnota (a) je null. Pokud je null, vrátí řízení.
		/// Pokud není null, sestaví chybovou hlášku a vyvolá chybu TestException.
		/// </summary>
		/// <param name="a">Hodnota A</param>
		/// <param name="error">Text chyby, může obsahovat proměnné %0 až %nn, doplní se z dalších parametrů (variables)</param>
		/// <param name="variables">Hodnoty do chybové hlášky error</param>
		public static void IsNull(object a, string error, params object[] variables)
		{
			if (a == null) return;
			ThrowTestException(error, variables);
		}
		/// <summary>
		/// Ověří, že hodnota (a) není null. Pokud není null, vrátí řízení.
		/// Pokud je null, sestaví chybovou hlášku a vyvolá chybu TestException.
		/// </summary>
		/// <param name="a">Hodnota A</param>
		/// <param name="error">Text chyby, může obsahovat proměnné %0 až %nn, doplní se z dalších parametrů (variables)</param>
		/// <param name="variables">Hodnoty do chybové hlášky error</param>
		public static void IsNotNull(object a, string error, params object[] variables)
		{
			if (a != null) return;
			ThrowTestException(error, variables);
		}
		/// <summary>
		/// Ověří, že hodnota (a) je true. Pokud je true, vrátí řízení.
		/// Pokud je false, sestaví chybovou hlášku a vyvolá chybu TestException.
		/// </summary>
		/// <param name="a">Hodnota</param>
		/// <param name="error">Text chyby, může obsahovat proměnné %0 až %nn, doplní se z dalších parametrů (variables)</param>
		/// <param name="variables">Hodnoty do chybové hlášky error</param>
		public static void IsTrue(bool a, string error, params object[] variables)
		{
			if (a == true) return;
			ThrowTestException(error, variables);
		}
		/// <summary>
		/// Ověří, že hodnota (a) je false. Pokud je false, vrátí řízení.
		/// Pokud je true, sestaví chybovou hlášku a vyvolá chybu TestException.
		/// </summary>
		/// <param name="a">Hodnota A</param>
		/// <param name="error">Text chyby, může obsahovat proměnné %0 až %nn, doplní se z dalších parametrů (variables)</param>
		/// <param name="variables">Hodnoty do chybové hlášky error</param>
		public static void IsFalse(bool a, string error, params object[] variables)
		{
			if (a == false) return;
			ThrowTestException(error, variables);
		}
		/// <summary>
		/// Ověří, zda dvě hodnoty (a) a (b) jsou shodné. Pokud ano, vrátí řízení.
		/// Pokud nejsou shodné, sestaví chybovou hlášku a vyvolá chybu TestException.
		/// V textu chybové hlášky lze použít řetězec %A a %B, na jejich místo pak bude vložen text z parametru (a) a (b).
		/// </summary>
		/// <param name="a">Hodnota A</param>
		/// <param name="b">Hodnota B</param>
		/// <param name="error">Text chyby, může obsahovat proměnné %0 až %nn, doplní se z dalších parametrů (variables)</param>
		/// <param name="variables">Hodnoty do chybové hlášky error</param>
		public static void IsEqual(object a, object b, string error, params object[] variables)
		{
			if (IsEqual(a, b)) return;
			string err = _GetTextWithVariables(error, a, b);
			ThrowTestException(err, variables);
		}
		/// <summary>
		/// Ověří, zda dvě hodnoty (a) a (b) jsou různé. Pokud ano, vrátí řízení.
		/// Pokud jsou shodné, sestaví chybovou hlášku a vyvolá chybu TestException.
		/// V textu chybové hlášky lze použít řetězec %A a %B, na jejich místo pak bude vložen text z parametru (a) a (b).
		/// </summary>
		/// <param name="a">Hodnota A</param>
		/// <param name="b">Hodnota B</param>
		/// <param name="error">Text chyby, může obsahovat proměnné %0 až %nn, doplní se z dalších parametrů (variables)</param>
		/// <param name="variables">Hodnoty do chybové hlášky error</param>
		public static void IsNotEqual(object a, object b, string error, params object[] variables)
		{
			if (!IsEqual(a, b)) return;
			string err = _GetTextWithVariables(error, a, b);
			ThrowTestException(err, variables);
		}
		/// <summary>
		/// Vrátí text (error), kde namísto %A vloží text z objektu (a) a namísto %B vloží text z (b).
		/// </summary>
		/// <param name="error"></param>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		private static string _GetTextWithVariables(string error, object a, object b)
		{
			if (error == null) return "";
			string result = error;
			if (result.Contains("%A"))
				result = result.Replace("%A", a.ToString());
			if (result.Contains("%B"))
				result = result.Replace("%B", b.ToString());
			return result;
		}
		/// <summary>
		/// Pokud je na vstupu předána hodnota true, vyvolá chybu. Hodnota false vrátí řízení.
		/// Typicky: Assert.ErrorIf(count > maximum, "text chyby");
		/// </summary>
		/// <param name="condition">Podmínka. Hodnota true = vyvolá chybu, false je v pořádku.</param>
		/// <param name="error">Text chyby, může obsahovat proměnné %0 až %nn, doplní se z dalších parametrů (variables)</param>
		/// <param name="variables">Hodnoty do chybové hlášky error</param>
		public static void ErrorIf(bool condition, string error, params object[] variables)
		{
			if (!condition) return;
			ThrowTestException(error, variables);
		}
		/// <summary>
		/// Sestaví chybovou hlášku a vyvolá chybu TestException
		/// </summary>
		/// <param name="error">Text chyby, může obsahovat proměnné %0 až %nn, doplní se z dalších parametrů (variables)</param>
		/// <param name="variables">Hodnoty do chybové hlášky error</param>
		public static void ThrowTestException(string error, params object[] variables)
		{
			MessageInfo msg = MessageInfo.Get(error, variables);
			throw new TestException(msg);
		}
		/// <summary>
		/// Vrací true, pokud parametry (a) a (b) jsou shodného typu a obsahují shodnou hodnotu.
		/// Vrací false, pokud je tomu jinak.
		/// </summary>
		/// <param name="a">Údaj A</param>
		/// <param name="b">Údaj B</param>
		/// <returns>true = shodný obsah  /  false = rozdílný obsah</returns>
		public static bool IsEqual(object a, object b)
		{
			return object.Equals(a, b);

			// Old:
			if (a.GetType() != b.GetType())
				return false;
			else if (a is bool && b is bool)
				return (((bool)a) == ((bool)b));
			else if (a is byte && b is byte)
				return (((byte)a) == ((byte)b));
			else if (a is Int16 && b is Int16)
				return (((Int16)a) == ((Int16)b));
			else if (a is Int32 && b is Int32)
				return (((Int32)a) == ((Int32)b));
			else if (a is Int64 && b is Int64)
				return (((Int64)a) == ((Int64)b));
			else if (a is UInt16 && b is UInt16)
				return (((UInt16)a) == ((UInt16)b));
			else if (a is UInt32 && b is UInt32)
				return (((UInt32)a) == ((UInt32)b));
			else if (a is UInt64 && b is UInt64)
				return (((UInt64)a) == ((UInt64)b));
			else if (a is DateTime && b is DateTime)
				return (((DateTime)a) == ((DateTime)b));
			else if (a is decimal && b is decimal)
				return (((decimal)a) == ((decimal)b));
			else if (a is float && b is float)
				return (((float)a) == ((float)b));
			else if (a is double && b is double)
				return (((double)a) == ((double)b));
			else if (a is char && b is char)
				return (((char)a) == ((char)b));
			else if (a is string && b is string)
				return (((string)a) == ((string)b));
			else if (a is SqlInt16 && b is SqlInt16)
				return (((SqlInt16)a).Equals((SqlInt16)b));
			else if (a is SqlInt32 && b is SqlInt32)
				return (((SqlInt32)a).Equals((SqlInt32)b));
			else if (a is SqlInt64 && b is SqlInt64)
				return (((SqlInt64)a).Equals((SqlInt64)b));
			else if (a is SqlDecimal && b is SqlDecimal)
				return (((SqlDecimal)a).Equals((SqlDecimal)b));
			else if (a is SqlDouble && b is SqlDouble)
				return (((SqlDouble)a).Equals((SqlDouble)b));
			else if (a is SqlDateTime && b is SqlDateTime)
				return (((SqlDateTime)a).Equals((SqlDateTime)b));
			else
				return a.Equals(b);
			return (a.ToString()) == (b.ToString());
		}
		/// <summary>
		/// Vrátí čas v sekundách, s maximální změřenou přesností
		/// </summary>
		/// <param name="time"></param>
		/// <returns></returns>
		public static string FormatTime(System.Diagnostics.Stopwatch time)
		{
			float seconds = (float)(time.ElapsedTicks) / (float)(System.Diagnostics.Stopwatch.Frequency);
			return seconds.ToString();
		}
	}
	/// <summary>
	/// Základní TEST exception
	/// </summary>
	public class TestException : Exception
	{
		/// <summary>Název typu chyby</summary>
		public virtual string ErrTyp { get { return "Chyba v testu v aplikaci ArchivingUtility"; } }
		/// <summary>Implicitní konstruktor</summary>
		public TestException()
		{ }
		/// <summary>Explicitní konstruktor</summary>
		/// <param name="message">Zpráva o chybě</param>
		public TestException(string message)
			: base(message)
		{ }
		/// <summary>Explicitní konstruktor</summary>
		/// <param name="message">Zpráva o chybě</param>
		/// <param name="inner">Vnitřní výjimka</param>
		public TestException(string message, Exception inner)
			: base(message, inner)
		{ }
	}
	#endregion
	#region CLASS TestResults a TestResultsItem : objekt pro zachycení výsledků testů, položka jednoho výsledku
	/// <summary>
	/// Třída, která v sobě shrnuje výsledky spuštěných testů
	/// </summary>
	public class TestResults
	{
		/// <summary>
		/// Privátní soupis testů
		/// </summary>
		private List<TestResultsItem> _ResultList;
		private int _LastAddIndex;
		/// <summary>
		/// Aktuální položka. Jde vždy o poslední přidanou položku.
		/// </summary>
		public TestResultsItem CurrentItem
		{
			get
			{
				if (this._LastAddIndex < 0 || this._LastAddIndex >= this._ResultList.Count) return null;
				return this._ResultList[this._LastAddIndex];
			}
		}
		/// <summary>
		/// Konstruktor objektu
		/// </summary>
		public TestResults()
		{
			this._ResultList = new List<TestResultsItem>();
		}
		/// <summary>
		/// Přidá nový záznam pro test
		/// </summary>
		/// <param name="methodInfo"></param>
		/// <param name="testName"></param>
		public void AddTest(MethodInfo methodInfo, string testName)
		{
			string assembly = methodInfo.ReflectedType.Assembly.FullName;
			string className = methodInfo.ReflectedType.Name;
			string testMethod = methodInfo.Name;
			this._ResultList.Add(new TestResultsItem(assembly, className, testMethod, testName));
			this._LastAddIndex = this._ResultList.Count - 1;
		}
		/// <summary>
		/// Nastaví úspěšně ukončení
		/// </summary>
		public void SetOK()
		{
			this.SetOK(null);
		}
		/// <summary>
		/// Nastaví úspěšně ukončení, uloží případnou výstupní hodnotu metody - pokud je to string
		/// </summary>
		/// <param name="result">Výstup testu. Pokud je to string, bude uložen do ReturnValue</param>
		public void SetOK(object result)
		{
			if (this._LastAddIndex < 0 || this._LastAddIndex >= this._ResultList.Count) return;
			TestResultsItem item = this._ResultList[this._LastAddIndex];
			item.Successfully = true;
			if (result is string)
			{
				string resString = (string)result;
				item.ReturnValue = resString;
			}
		}
		/// <summary>
		/// Nastaví ukončení s chybou
		/// </summary>
		public void SetError(Exception exc)
		{
			if (this._LastAddIndex < 0 || this._LastAddIndex >= this._ResultList.Count) return;
			TestResultsItem item = this._ResultList[this._LastAddIndex];
			item.ErrorSection = Assert.Section;
			item.DetectError = true;
			item.ErrorStack = exc.StackTrace;
			// Nalezení nejvnitřnější chyby, jejího stacku a jeho řádku s chybou:
			string errorPoint;
			Exception innerExc = Throw.GetError(exc, out errorPoint);
			string errorInfo = innerExc.Message;
			if (errorPoint != null)
				errorInfo += "\r\n" + errorPoint;
			item.ErrorMessage = errorInfo;
		}
		/// <summary>
		/// Do aktuálního záznamu vloží čas dle stopek
		/// </summary>
		/// <param name="sw"></param>
		public void SetTime(Stopwatch sw)
		{
			if (this._LastAddIndex < 0 || this._LastAddIndex >= this._ResultList.Count) return;
			TestResultsItem item = this._ResultList[this._LastAddIndex];
			item.TestDuration = sw.Elapsed;
			item.TestDurationTicks = sw.ElapsedTicks;
		}
	}
	/// <summary>
	/// Třída pro uložení výsledků jednoho běhu testu
	/// </summary>
	public class TestResultsItem
	{
		public string Assembly;
		public string ClassName;
		public string TestMethod;
		public string TestName;
		/// <summary>
		/// Čas testu
		/// </summary>
		public TimeSpan TestDuration;
		/// <summary>
		/// Počet ticků časovače, určuje dobu testu.
		/// Frekvenci časovače (počet ticků za sekundu) lze zjistit v Stopwatch.Frequency
		/// </summary>
		public long TestDurationTicks;
		public bool Successfully;
		public string ReturnValue;
		public bool DetectError;
		/// <summary>
		/// Sekce testu, v němž došlo k chybě (Assert.Section)
		/// </summary>
		public string ErrorSection;
		public string ErrorMessage;
		public string ErrorStack;
		public TestResultsItem(string assembly, string className, string testMethod, string testName)
		{
			this.Assembly = assembly;
			this.ClassName = className;
			this.TestMethod = testMethod;
			this.TestName = testName;
		}
		/// <summary>
		/// Jméno testu: buď vygenerované (třída.metoda) anebo jméno ze záhlaví testu (atribut TestPoint)
		/// </summary>
		public string Name
		{
			get
			{
				string name = this.TestName;
				if (String.IsNullOrEmpty(name))
					name = this.ClassName + "." + this.TestMethod;
				return name;
			}
		}
		/// <summary>
		/// Stav testu: (Running / OK / Error: )
		/// </summary>
		public string State
		{
			get
			{
				if (this.Successfully)
					return "OK";
				if (this.DetectError)
					return "Error: " + this.ErrorMessage; 
				return "now running...";
			}
		}
		/// <summary>
		/// Čas testu
		/// </summary>
		public string Time
		{
			get
			{
				if (this.TestDurationTicks <= 0) return "";
				float msec = (float)this.TestDurationTicks / (float)Stopwatch.Frequency * 1000;         // milisekund
				string time = msec.ToString() + " milisec";
				return time;
			}
		}
		/// <summary>
		/// Vizualizace objektu
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return this.Name + ": " + this.State + (this.TestDurationTicks > 0 ? "; " : "") + this.Time;
		}
	}
	#endregion
	#region ATRIBUTY Testable, TestPoint
	/// <summary>
	/// Označuje třídu, která má přinejmenším jednu metodu, která je vstupním bodem testu
	/// </summary>
	[Serializable]
	[AttributeUsage(AttributeTargets.Class, Inherited = false)]
	public class TestableAttribute : Attribute
	{
		public TestableAttribute()
		{}
	}
	/// <summary>
	/// Označuje metodu, která je vstupním bodem testu. Musí být public static a bezparametrická.
	/// </summary>
	[Serializable]
	[AttributeUsage(AttributeTargets.Method, Inherited = false)]
	public class TestPointAttribute : Attribute
	{
		/// <summary>
		/// Vstupní bod testu se zadáním názvu testu a možností dočasně test vypustit (zadáním hodnoty scope = TestScopeType.Disabled)
		/// </summary>
		/// <param name="name">Jméno testu</param>
		/// <param name="scope">Okruh testu - při jakých příležitostech se má spouštět</param>
		public TestPointAttribute(string name, TestScopeType scope)
		{
			_Name = name;
			_TestScope = scope;
		}
		public string Name { get { return _Name; } }
		private string _Name;
		public TestScopeType TestScope { get { return _TestScope; } }
		private TestScopeType _TestScope;
	}
	#endregion
	#region FLAGS TestScopeType : typy testů
	/// <summary>
	/// Příležitost, při které se test má spouštět.
	/// Příležitosti lze sumarizovat (bitwise-or), anebo použít předdefinované sumární hodnoty.
	/// </summary>
	[Flags]
	public enum TestScopeType
	{
		/// <summary>
		/// Žádný test
		/// </summary>
		None = 0,
		/// <summary>
		/// Zcela základní, nejnižší úroveň funkcionality.
		/// Test by se měl spouštět vždy.
		/// Test by měl být co nejrychlejší.
		/// </summary>
		Essential = 0x0001,
		/// <summary>
		/// Standardní funkcionalita.
		/// Test by se měl spouštět při běžném startu aplikace.
		/// Test by měl být co nejrychlejší.
		/// </summary>
		Standard = 0x0002,
		/// <summary>
		/// Rozšířený test.
		/// Nespouští se při běžném startu aplikace, ale na požádání v menu aplikace.
		/// Test nemusí být optimalizován na rychlost.
		/// </summary>
		Enhanced = 0x0004,
		/// <summary>
		/// Zátěžový test.
		/// Nespouští se při běžném startu aplikace, ale na požádání v menu aplikace při volbě [x] Provést i zátěžové testy.
		/// Test nemusí být optimalizován na rychlost.
		/// </summary>
		Workload = 0x0008,
		/// <summary>
		/// Vývojový test.
		/// Tyto testy se provádějí na požadavek z vývojářského okna.
		/// </summary>
		Development = 0x0100,
		/// <summary>
		/// Vývojový test pro optimalizaci časů.
		/// Tyto testy se provádějí na požadavek z vývojářského okna.
		/// Typicky provádějí více opakování jednotkového testu s různým množstvím dat, a měří přesný čas průběhu, 
		/// za účelem odhalení případného časového zlomu (nelineární závislost času na množství).
		/// </summary>
		TimeOptimize = 0x0200,
		/// <summary>
		/// Zakázané testy
		/// </summary>
		Disabled = 0x8000,
		/// <summary>
		/// Všechny testy
		/// </summary>
		All = 0x7FFF

	}
	#endregion
	#region CLASS TestLibrary : pomocné metody pro různé testy, obecně zaměřené, nespecifické
	/// <summary>
	/// TestLibrary : pomocné metody pro různé testy, obecně zaměřené, nespecifické
	/// </summary>
	public static class TestLibrary
	{
		/// <summary>
		/// Naformátuje data matice do textu, který lze vložit do Excelu jako tabulka pro tvorbu grafu typu X/Y.
		/// 
		/// </summary>
		/// <param name="mainTitle">Hlavní titulek. Jde do pozice B1.</param>
		/// <param name="matrix">Obsah matrixu = data. 
		/// Data jdou do pozic B4 a dále (vpravo, dolů). 
		/// Index 0 = řádky. Index 1 = sloupce. Index 3 = hodnota, čte se z pozice 0.</param>
		/// <param name="rowTitle">Záhlaví jednotlivých řádků. Ukládá se do buňky A3. Typicky popisuje hodnoty X.</param>
		/// <param name="rowValues">Popisky hodnot X, význam řádků. Ukládá se do sloupce A od řádku 4 dolů.</param>
		/// <param name="colTitle">Záhlaví jednotlivých sloupců. Ukládá se do buňky B2.</param>
		/// <param name="colValues">Popisky hodnot Y, význam sloupců. Ukládá se do řádku 3 od sloupce B doprava.</param>
		/// <returns>Zformátovaný matrix.</returns>
		public static string FormatMatrix(string mainTitle, float[, ,] matrix, string rowTitle, int[] rowValues, string colTitle, int[] colValues)
		{
			System.Text.StringBuilder sb = new StringBuilder();
			string line;
			int rowCount = rowValues.Length;
			int colCount = colValues.Length;

			// Titulkový řádek:
			line = "\t" + (mainTitle == null ? "VÝSLEDNÝ GRAF" : mainTitle);
			sb.AppendLine(line);

			// Titul sloupců (osy Y):
			line = "\t" + (colTitle == null ? "Obsah sloupců" : colTitle);
			sb.AppendLine(line);

			// Záhlaví sloupců (labely sloupců):
			line = (rowTitle == null ? "Význam řádků" : rowTitle);
			for (int col = 0; col < colCount; col++)
				line += "\t" + colValues[col].ToString();
			sb.AppendLine(line);

			// Jednotlivé řádky s daty:
			for (int row = 0; row < rowCount; row++)
			{
				line = rowValues[row].ToString();
				for (int col = 0; col < colCount; col++)
					line += "\t" + matrix[row, col, 0].ToString();
				sb.AppendLine(line);
			}

			return sb.ToString();
		}

	}
	#endregion
	#region CLASS CommonTestBase : Podkladová třída pro veškeré testovací třídy.
	/// <summary>
	/// CommonTestBase: Podkladová třída pro veškeré testovací třídy.
	/// Obsahuje základní metody pro podporu časomíry a jiné užitečné drobnosti.
	/// Zatím není povinné ji používast.
	/// </summary>
	public class CommonTestBase
	{
		#region SPOLEČNÉ KONTROLNÍ METODY, PODPORA
		/// <summary>
		/// Kontrola nalezeného záznamu, zda je to záznam odpovídající očekávání (not null, class, recordnumber)
		/// </summary>
		/// <param name="record">Záznam s daty</param>
		/// <param name="classNumber">Očekávané číslo třídy</param>
		/// <param name="recordNumber">Očekávané číslo záznamu</param>
		/// <param name="className">Název třídy</param>
		/// <param name="refer">Očekávaná reference</param>
		/// <param name="name">Očekávaný název</param>
		protected static void RecordCheck(Noris.Schedule.Support.Data.RecordCls record, int classNumber, int recordNumber, string className, string refer, string name)
		{
			Assert.IsNotNull(record, "Nebyl nalezen záznam číslo #%0: %1 třídy %2: %3.", classNumber, className, recordNumber, refer);
			Assert.IsEqual(record.ClassNumber, classNumber, "Byl nalezen záznam jiné třídy (%A) než bylo očekáváno: %B: %0.", className);
			if (recordNumber > 0)
				Assert.IsEqual(record.RecordNumber, recordNumber, "Byl nalezen jiný záznam (%A: %0) než bylo očekáváno: %B: %1.", record.Reference, refer);
			if (refer != null)
				Assert.IsEqual(record.Reference, refer, "Byl nalezen jiný záznam s jinou referencí (%A) než bylo očekáváno: %B.");
			if (name != null)
				Assert.IsEqual(record.Nazev, name, "Byl nalezen jiný záznam s jiným názvem (%A) než bylo očekáváno: %B.");
		}
		#endregion
		#region PODPORA ČASOMÍRY
		/// <summary>
		/// Stopky. Jsou autinstanční, ale nejsou nastartované. Aplikace je musí nastartovat, např. metodou StopWatchStart()
		/// </summary>
		protected static Stopwatch StopWatch
		{
			get
			{
				if (_StopWatch == null)
					_StopWatch = new Stopwatch();
				return _StopWatch;
			}
		}
		private static Stopwatch _StopWatch;
		/// <summary>
		/// Vynuluje a spustí stopky
		/// </summary>
		protected static void StopWatchStart()
		{
			StopWatch.Reset();
			StopWatch.Start();
		}
		/// <summary>
		/// Zastaví stopky a vrátí jejich čas v milisekundách
		/// </summary>
		protected static float StopWatchStop()
		{
			StopWatch.Stop();
			return StopWatchTimeMilisec();
		}
		/// <summary>
		/// Vrátí aktuálně dosažený čas stopek v milisekundách
		/// </summary>
		/// <returns></returns>
		protected static float StopWatchTimeMilisec()
		{
			return 1000F * (float)StopWatch.ElapsedTicks / (float)Stopwatch.Frequency;
		}
		/// <summary>
		/// Vrátí čas daný v počtu Ticks převedený na milisekundy
		/// </summary>
		/// <param name="ticks"></param>
		/// <returns></returns>
		protected static float StopWatchGetFromTicksMilisec(long ticks)
		{
			return 1000F * (float)(ticks) / (float)Stopwatch.Frequency;
		}
		/// <summary>
		/// Vrátí čas daný v počtu Ticks převedený na mikrosekundy
		/// </summary>
		/// <param name="ticks"></param>
		/// <returns></returns>
		protected static float StopWatchGetFromTicksMicrosec(long ticks)
		{
			return 1000000F * (float)(ticks) / (float)Stopwatch.Frequency;
		}
		#endregion
	}
	#endregion
}
